package com.cy.pj.sys.service.aspect;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.cy.pj.sys.util.IPUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class SysLogAspect {

    /**
     * @Pointcut 注解用于描述方法，定义切入点
     * bean(spring中bean的名字)为切入点表达式中一种定义切入点的方式
     * 表示名字为sysUserServiceImpl的所有的bean对象中任意一个方法执行时都要进行功能拓展
     */
    //@Pointcut("bean(sysUserServiceImpl)")
    //@annotation可以细粒度的对使用自定义的注解@RequiredLog描述的方法进行功能拓展
    @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
    public void doLog(){}//方法内部不需要写任何代码，只是用于承载@Pointcut注解

    /**
     * @Around 注解描述的方法为一个业务拓展方法，在aop中通常称之为环绕通知
     * 此注解中value属性的值为切入点，这些切入点方法执行时会执行@Around注解描述的方法
     * @param proceedingJoinPoint 表示连接点，@Around注解描述的方法参数必须为这个类型
     *                            这个连接点对应的是切入点集合中某个目标方法正在执行的业务
     * @return 目标方法的返回值，在@Around描述的方法，返回值必须为Object类型
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        long s1=System.currentTimeMillis();
        log.info("start {}",s1);
        try {
            Object result=proceedingJoinPoint.proceed();//执行目标业务方法
            long s2=System.currentTimeMillis();
            log.info("end {}",s2);
            //调用保存日志的方法
            saveUserLog(proceedingJoinPoint, s2-s1);
            return result;
        }catch (Throwable e){
            log.info("error {}",e.getMessage());
            throw e;
        }
    }

    @Autowired
    private SysLogService sysLogService;
    private void saveUserLog(ProceedingJoinPoint proceedingJoinPoint,long time) throws Throwable {
        //找到目标类
        Class<?> targetClass=proceedingJoinPoint.getTarget().getClass();
        //找到目标方法和方法的签名(proceedingJoinPoint封装了正在执行的目标方法的相关信息，所以要用这个对象取)
        MethodSignature ms= (MethodSignature) proceedingJoinPoint.getSignature();
        Method targetMethod=targetClass.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
        //找到目标方法上的注解
        RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
        //获取注解中operation属性的值
        String operation=requiredLog.operation();
        String username="admin";
        //获取方法信息(方法所在类的类全名+方法名)
        String method=targetClass.getName()+targetMethod.getName();
        String ip= IPUtils.getIpAddr();
        /**
         * 获取实际调用方法时传入的参数信息
         * new ObjectMapper().writer().writeValueAsString()把结果转成Json格式
         * 假如能转换为Json格式尽量转换为Json格式
         */
        String param=new ObjectMapper().writer().writeValueAsString(proceedingJoinPoint.getArgs());
        //封装用户行为日志
        SysLog log=new SysLog();
        log.setUsername(username);
        log.setOperation(operation);
        log.setMethod(method);
        log.setIp(ip);
        log.setParams(param);
        log.setTime(time);
        sysLogService.saveUserLog(log);
    }
}
